﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using LockerRack.Exceptions;
using LockerRack.Helpers;
using LockerRack.Interfaces;
using Microsoft.Practices.Unity;

namespace LockerRack.Drawers
{
    /// <summary>
    /// This class is a descriptor for a folder content. 
    /// The xml stuff here - it is descriptor of drawer itself
    /// </summary>
    [XmlRoot("Drawer")]
    public class Drawer
    {
        [XmlIgnore]
        public IUnityContainer TypesContainer { get; private set; }
        [XmlIgnore]
        public Drawer Parent { get; set; }
        [XmlIgnore]
        public string FilesFolder { get; set; }

        public static Drawer OpenDrawerPlain(InitParams initParams)
        {
            if (initParams == null)
                return null;

            if (!PathUtility.CheckDirectory(initParams.Folder))
            {
                initParams.Exception = new LockerException(string.Format("Can't open folder {0}", initParams.Folder));
                return null;
            }

            Drawer drawer;
            LockerException ex;
            if ((drawer = Deserialize(initParams.Folder, out ex)) == null)
            {
                initParams.Exception = ex;
                return null;
            }

            drawer.TypesContainer = initParams.TypesContainer;
            drawer.Folder = initParams.Folder;
            drawer.Parent = initParams.Parent;
            drawer.FilesFolder = initParams.FilesFolder;

            var exceptions = new List<Exception>();
            drawer.LoadFiles(exceptions);
           
            if (exceptions.Count > 0)
            {
                initParams.Exception = new LockerException(exceptions);
            }

            return drawer;
        }

        public static Drawer OpenDrawerTree(InitParams initParams)
        {
            if (initParams == null)
                return null;

            if (!PathUtility.CheckDirectory(initParams.Folder))
            {
                initParams.Exception = new LockerException(string.Format("Can't open folder {0}", initParams.Folder));
                return null;
            }

            Drawer drawer;
            LockerException ex;
            if ((drawer = Deserialize(initParams.Folder, out ex)) == null)
            {
                initParams.Exception = ex;
                return null;
            }

            drawer.TypesContainer = initParams.TypesContainer;
            drawer.Folder = initParams.Folder;
            drawer.Parent = initParams.Parent;
            drawer.FilesFolder = initParams.FilesFolder;

            var exceptions = new List<Exception>();
            drawer.LoadFiles(exceptions);
            drawer.LoadDrawersTree(exceptions);

            if (exceptions.Count > 0)
            {
                initParams.Exception = new LockerException(exceptions);
            }

            return drawer;
        }

        public void AddChild(Drawer drawer)
        {
            var found = Drawers.Find(d => d.Id == drawer.Id);
            if (found != null)
                return;

            drawer.Parent = this;
            Drawers.Add(drawer);
        }

        public void RemoveChild(Drawer drawer)
        {
            var found = Drawers.Find(d => d.Id == drawer.Id);
            if (found == null)
                return;

            drawer.Parent = null;
            Drawers.Remove(found);
        }

        public void MoveDrawer(Drawer drawer, Drawer source, Drawer target)
        {
            source.RemoveChild(drawer);
            target.AddChild(drawer);
        }

        private static Drawer Deserialize(string folder, out LockerException ex)
        {
            ex = null;
            var filename = Path.Combine(folder, Constants.DRAWER_FILE_NAME);
            if (!File.Exists(filename))
            {
                //no file; but this is not an exception
                return null;
            }

            var serializer = new DataSerializer<Drawer>();
            var drawer = serializer.ReadFile(filename, out ex);

            if (drawer == null)
            {
                ex = new LockerException(string.Format("Can't read file {0}", filename));
                return null;
            }

            drawer.Folder = folder;

            return drawer;
        }

        internal void Save(string rootToSave, out LockerException ex)
        {
            ex = null;
            var folder = Path.Combine(rootToSave, Folder);
            if (!PathUtility.CheckDirectory(folder))
                return;

            var exceptions = new List<Exception>();

            if (!Serialize(folder, exceptions)) 
                return;

            this.SaveFiles(folder, exceptions);
            this.SaveDrawers(rootToSave, exceptions);
            if(exceptions.Count > 0)
            {
                ex = new LockerException(exceptions);
            }
        }

        private bool Serialize(string folder, List<Exception> exceptions)
        {
            var filename = Path.Combine(folder, Constants.DRAWER_FILE_NAME);
            var serializer = new DataSerializer<Drawer>();
            LockerException ex;
            if (!serializer.WriteFile(filename, this, out ex))
            {
                exceptions.Add(ex);
                return false;
            }
            return true;
        }

        public Drawer()
        {
            Name = "Empty";
            Id = Guid.NewGuid();
            Folder = IdStr;
            Files = new List<IDrawerFile>();    
            Drawers = new List<Drawer>();
        }

        [XmlAttribute]
        public string Name { get; set; }

        [XmlAttribute("Id")]
        public string IdStr
        {
            get { return Id.ToString(); } 
            set
            {
                Guid id;
                Id = Guid.TryParse(value, out id) ? id : Guid.NewGuid();
            }
        }

        [XmlAttribute("ParentId")]
        public string ParentIdStr
        {
            get
            {
                return ParentId.ToString();
            } 
            set
            {
                Guid id;
                ParentId = Guid.TryParse(value, out id) ? id : Guid.NewGuid();
            }
        }

        private List<string> _children;
        [XmlElement("Children")]
        public List<string> Children
        {
            get
            {
                if(Drawers != null && Drawers.Count > 0)
                {
                    return Drawers.Select(d => d.IdStr).ToList();
                }
                return _children;
            }
            set { _children = value; }
        }

        private Guid _parentId;
        [XmlIgnore]
        public Guid ParentId
        {
            get
            {
                if (Parent != null)
                    return Parent.Id;

                return _parentId;
            } 
            set
            {
                if (Parent != null)
                    return;

                _parentId = value;
            }
        }

        [XmlIgnore]
        public Guid Id { get; set; }

        [XmlIgnore]
        public string Folder { get; set; }

        [XmlIgnore]
        public List<IDrawerFile> Files { get; set; }
        
        [XmlIgnore]
        public List<Drawer> Drawers { get; set; }

        public IDrawerFile FindFileById(Guid guid)
        {
            var found = FindFileByIdInFiles(guid, Files);
            if(found != null)
                return found;

            foreach (var drawer in Drawers)
            {
                found = drawer.FindFileById(guid);
                if (found != null)
                    return found;
            }

            return null;
        }

        public bool MoveDrawerFileTo(Drawer target, IDrawerFile file, out LockerException ex)
        {
            ex = null;

            if (this == target)
                return true;

            if(Files.Remove(file))
            {
                target.Files.Add(file);
                return true;    
            }
            return false;
        }

        public bool MoveDrawerFileByIdTo(Drawer target, IDrawerFile file, out LockerException ex)
        {
            ex = null;

            if (this == target)
                return true;

            var content = file.With(f => f.Contents);
            if(content == null)
                return false;

            var foundInSource = RemoveFileById(file.Contents.Id);
            if (foundInSource == null)
                return false;

            target.Files.Add(foundInSource);
            return true;
        }

        private IDrawerFile RemoveFileById(Guid guid)
        {
            if(Files == null || Files.Count < 1)
                return null;

            for (int i = 0; i < Files.Count; i++)
            {
                var file = Files[i];
                if (file.Contents == null)
                    continue;

                if (file.Contents.Id == guid)
                {
                    Files.Remove(file);
                    return file;
                }
            }

            return null;
        }

        public bool MoveDrawerTo(Drawer target, Drawer movingDrawer)
        {
            if(Drawers.Remove(movingDrawer))
            {
                target.Drawers.Add(movingDrawer);
                return true;
            }

            return false;
        }

        private IDrawerFile FindFileByIdInFiles(Guid guid, List<IDrawerFile> files)
        {
            foreach (var file in files)
            {
                if (file.Contents.Id == guid)
                    return file;
            }
            return null;
        }

        public class InitParams
        {
            public string Folder;
            public string FilesFolder;
            public IUnityContainer TypesContainer;
            public LockerException Exception;
            public Drawer Parent;
        }
    }
}
